package publisher;

import java.io.IOException;

import aimpublisher.AIMPublisher;

import com.google.code.facebookapi.FacebookException;
import com.google.code.facebookapi.FacebookJsonRestClient;

import configuration.EntryExists;
import configuration.NoEntryException;

// STATE_INITIAL = 0
// STATE_ACQUIRING = 1
// STATE_HASTOKEN = 2
// STATE_HASSESSION = 3
public class Facebook extends Publisher {
	// OVERVIEW: The Facebook module is a Publisher module that utilizes the
	// Facebook service.
	// AF(c) = Concrete implementation via an external library's abstraction of
	// an application on the Facebook service
	// REP(c) = c.apiKey != null && c.secret != null && c.facebookClient != null
	private final String apiKey = "b3700b2994d5fa2e1a91de0327a65f4f";
	private final String secret = "c866aba12cea423b742762eab79fcbf3";

	// A FacebookRestClient that uses the JSON result format
	private FacebookJsonRestClient facebookClient = new FacebookJsonRestClient(
			this.apiKey, this.secret);

	// REQUIRES: true
	// MODIFIES: this
	// EFFECTS: Constructs a new instance of the Facebook module.
	public Facebook() {
		// Inform the external library that this is a desktop application. This
		// is VITAL for proper communication with the Facebook service;
		// otherwise, we'll receive invalid signature exceptions at every turn
		// because we're using the application secret and not the session
		// secret.
		this.facebookClient.setIsDesktop(true);
	}

	// REQUIRES: -1 < state < 4
	// MODIFIES: AIMPublisher.getConfiguration()
	// EFFECTS: Modifies the Configuration per the new state. Removes any
	// Configuration entries that should not exist per the new state, which is
	// necessary to prevent stack overflows.
	public void revertState(final int state) {
		// authToken sessionKey sessionSecret
		// STATE_INITIAL
		// STATE_ACQUIRING
		// STATE_HASTOKEN x
		// STATE_HASSESSION x x x
		if (state < 2) { // STATE_HASTOKEN
			if (AIMPublisher.getConfiguration().isEntry("Facebook",
					this.getIdentity(), "authToken")) {
				try {
					AIMPublisher.getConfiguration().removeEntry("Facebook",
							this.getIdentity(), "authToken");
				} catch (final NoEntryException e) {
					// can't happen
				}
			}
		}
		if (state < 3) {
			if (AIMPublisher.getConfiguration().isEntry("Facebook",
					this.getIdentity(), "sessionKey")) {
				try {
					AIMPublisher.getConfiguration().removeEntry("Facebook",
							this.getIdentity(), "sessionKey");
				} catch (final NoEntryException e) {
					// can't happen
				}
			}
			if (AIMPublisher.getConfiguration().isEntry("Facebook",
					this.getIdentity(), "sessionSecret")) {
				try {
					AIMPublisher.getConfiguration().removeEntry("Facebook",
							this.getIdentity(), "sessionSecret");
				} catch (final NoEntryException e) {
					// can't happen
				}
			}
		}
	}

	@SuppressWarnings("deprecation")
	@Override
	// REQUIRES:
	public String run(final String message) {
		String authToken = null;
		String sessionKey = null;
		String sessionSecret = null;

		// Retrieve an authorization token
		if (AIMPublisher.getConfiguration().isEntry("Facebook",
				this.getIdentity(), "authToken")) {
			try {
				authToken = AIMPublisher.getConfiguration().getEntry(
						"Facebook", this.getIdentity(), "authToken");
			} catch (final NoEntryException e) {
				// can't happen
			}
		}

		// Retrieve the session key
		if (AIMPublisher.getConfiguration().isEntry("Facebook",
				this.getIdentity(), "sessionKey")) {
			try {
				sessionKey = AIMPublisher.getConfiguration().getEntry(
						"Facebook", this.getIdentity(), "sessionKey");
			} catch (final NoEntryException e) {
				// can't happen
			}
		}

		// Retrieve the session secret
		if (AIMPublisher.getConfiguration().isEntry("Facebook",
				this.getIdentity(), "sessionSecret")) {
			try {
				sessionSecret = AIMPublisher.getConfiguration().getEntry(
						"Facebook", this.getIdentity(), "sessionSecret");
			} catch (final NoEntryException e) {
				// can't happen
			}
		}

		// Retrieve the old state
		final int oldState = this.getState();

		// Modify the new state per the Configuration
		if (authToken == null) {
			this.setState(1); // STATE_ACQUIRING
		} else if ((sessionKey == null) || (sessionSecret == null)) {
			this.setState(2); // STATE_HASTOKEN
		} else {
			this.setState(3); // STATE_HASSESSION
		}

		// STATE_INITIAL -> STATE_HASSESSION
		if ((oldState == 0) && (this.getState() > 1)) {
			return "Please specify a message.";
		}

		switch (this.getState()) {
		case 1: // STATE_ACQUIRING
			try {
				// Acquire an authentication token
				authToken = this.facebookClient.auth_createToken();
			} catch (final FacebookException e) {
				e.printStackTrace();
				return null;
			} catch (final IOException e) {
				e.printStackTrace();
				return null;
			}

			// Store an authentication token for later retrieval
			try {
				if (AIMPublisher.getConfiguration().isEntry("Facebook",
						this.getIdentity(), "authToken")) {
					AIMPublisher.getConfiguration().changeEntry("Facebook",
							this.getIdentity(), "authToken", authToken);
				} else {
					AIMPublisher.getConfiguration().addEntry("Facebook",
							this.getIdentity(), "authToken", authToken);
				}
			} catch (final EntryExists e) {
				// can't happen
			} catch (final NoEntryException e) {
				// can't happen
			}

			// Respond with an authentication token and a Publisher response
			return "http://www.facebook.com/login.php?api_key=" + this.apiKey
					+ "&auth_token=" + authToken + "\r\n"
					+ "Please specify a message.";

		case 2: // STATE_HASTOKEN
			// Acquire the session key and the session secret
			try {
				sessionKey = this.facebookClient.auth_getSession(authToken);
				sessionSecret = this.facebookClient.getSessionSecret();
			} catch (final FacebookException e) {
				// Revert to STATE_ACQUIRING to acquire an authentication token
				this.revertState(1);
				return this.run(message);
			} catch (final IOException e) {
				e.printStackTrace();
				return null;
			}

			// Store the session key and the session secret for later retrieval
			try {
				if (AIMPublisher.getConfiguration().isEntry("Facebook",
						this.getIdentity(), "sessionKey")) {
					AIMPublisher.getConfiguration().changeEntry("Facebook",
							this.getIdentity(), "sessionKey", sessionKey);
				} else {
					AIMPublisher.getConfiguration().addEntry("Facebook",
							this.getIdentity(), "sessionKey", sessionKey);
				}
				if (AIMPublisher.getConfiguration().isEntry("Facebook",
						this.getIdentity(), "sessionSecret")) {
					AIMPublisher.getConfiguration().changeEntry("Facebook",
							this.getIdentity(), "sessionSecret", sessionSecret);
				} else {
					AIMPublisher.getConfiguration().addEntry("Facebook",
							this.getIdentity(), "sessionSecret", sessionSecret);
				}
			} catch (final EntryExists e) {
				// can't happen
			} catch (final NoEntryException e) {
				// can't happen
			}

			// Switch to STATE_HASSESSION and handle the Communication message
			return this.run(message);

		case 3: // STATE_HASSESSION
			// A FacebookRestClient that uses the JSON result format (set the
			// session key)
			this.facebookClient = new FacebookJsonRestClient(this.apiKey,
					this.secret, sessionKey);

			// Inform the external library that this is a desktop application.
			// This
			// is VITAL for proper communication with the Facebook service;
			// otherwise, we'll receive invalid signature exceptions at every
			// turn
			// because we're using the application secret and not the session
			// secret.
			this.facebookClient.setIsDesktop(true);

			// Set the session secret
			this.facebookClient.setSessionSecret(sessionSecret);

			// Set the FBML for a user's profile, including the content for
			// the Wall tab and for the Boxes tab
			try {
				final long userId = this.facebookClient
						.auth_getUserId(authToken);
				this.facebookClient.profile_setFBML(userId, message, "", "",
						message);
			} catch (final FacebookException e) {
				// Revert to STATE_ACQUIRING to acquire an authentication token
				this.revertState(1);
				return this.run(message);
			} catch (final IOException e) {
				e.printStackTrace();
				return null;
			}

			this.setState(0); // STATE_INITIAL
			return "The message was published to Facebook successfully.";
		}

		return null;
	}
}